package com.itedge.infrastructure.web.controller.task.impl;

import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.task.Task;
import org.springframework.context.MessageSource;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.itedge.infrastructure.constants.InfrastructureConstants;
import com.itedge.infrastructure.domain.IProcessEntity;
import com.itedge.infrastructure.taskdata.ITaskDataEntity;
import com.itedge.infrastructure.taskdata.TaskDataUtil;
import com.itedge.infrastructure.util.ContextUtil;
import com.itedge.infrastructure.util.WebUtil;
import com.itedge.infrastructure.web.controller.task.ITaskController;
import com.itedge.infrastructure.web.dto.TaskAvailabilityResponse;
import com.itedge.infrastructure.web.taskhandler.ITaskDataHandler;
import com.itedge.infrastructure.web.taskhandler.TaskHandlerException;

/** 
 * Abstract base class for all task controller with {@link IProcessEntity} process object.
 * 
 * @author jhe
 *
 */
public abstract class AbstractTaskController implements ITaskController {
	
	/**
	 * Message source used for translations.
	 */
	protected MessageSource messageSource;
	
	/**
	 * Task name parameter, used in request mapping.
	 */
    protected static final String TASK_NAME_PARAM = "taskName";	
    
    /**
     * Process id parameter.
     */
    protected static final String PROC_ID = "procId";
    
    /**
     * Task history parameter.
     */
	protected static final String TASK_HISTORY_PARAM = "isHistory";
	
	/**
	 * Path for undefined task, controller will delegate to this view if task name is not found in taskHandlerMapping.
	 */
	protected static final String UNDEFINED_TASK = "undefindedTask";
	
	/**
	 * Key for model attribute of message, that task was completed in meantime.
	 */
	protected static final String MODEL_ATTR_TASK_COMPLETED = "taskCompletedMessage";
	
	/**
	 * Key for putting and retrieving task object id from session.
	 */
	protected static final String SESSION_ATTR_TASK_ID = "taskId";
	
	/**
	 * Key for putting and retrieving process id from session.
	 */
	protected static final String SESSION_ATTR_PROC_ID = "linkedObjectId";
	
	/**
	 * Key for putting and retrieving task data object from session.
	 */
	protected static final String SESSION_ATTR_TASK_DATA_OBJECT = "taskDataObject";
	
	/**
	 * Map with task handler mapping, to be initialized and filled in concrete subclasses.
	 */
	protected Map<String,ITaskDataHandler> taskHandlerMapping = new HashMap<String,ITaskDataHandler>();
	
	/**
	 * Default constructor, only argument is messageSource.
	 * 
	 * @param messageSource
	 */
	protected AbstractTaskController(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	/**
	 * Method for retrieving {@link Task} living task instances, to be overridden in concrete subclasses.
	 * 
	 * @param id
	 * @return task instance for given task id
	 */
	protected abstract Task getCurrentTask(String id);
	
	/**
	 * Method for retrieving {@link HistoricTaskInstance} historic task instances, to be overridden in concrete subclasses.
	 * 
	 * @param id
	 * @return historic task instance for given task id
	 */
	protected abstract HistoricTaskInstance getHistoryTask(String id);
	
	/**
	 * Method for retrieving linked {@link IProcessEntity} linked process objects by their procId.
	 * 
	 * @param procId
	 * @return linked process entity instance for specified process instance id
	 */
	protected abstract IProcessEntity getLinkedObject(String procId);
	
	/**
	 * Returns task view path.
	 * 
	 * @return task view path
	 */
	protected abstract String getTaskViewPath();
	
	/**
	 * Returns linked object view path.
	 * 
	 * @return linked entity view path
	 */
	protected abstract String getLinkedObjectViewPath();
	
	/**
	 * Returns name of linked object attribute - to be put in MVC model. 
	 * 
	 * @return linked entity model key
	 */
	protected abstract String getLinkedObjectAttrName();
	
	@Override
	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public String showTask(@PathVariable("id") String id, 
			@RequestParam(value = TASK_HISTORY_PARAM, required = true) Boolean historyParam, 
			@RequestParam(value = PROC_ID, required = true) String procId, Model model, 
			HttpServletRequest request) throws TaskHandlerException {
        Task currentTask = null;
        HistoricTaskInstance historyTask = null;
        IProcessEntity linkedObject = getLinkedObject(procId);
        if (Boolean.FALSE.equals(historyParam)) {
        	request.setAttribute(TASK_HISTORY_PARAM, false);
            currentTask = getCurrentTask(id); 
            // living task instance requested, but already completed by other actor
            if (currentTask == null) {
            	historyTask = getHistoryTask(id);
            	if (historyTask != null) {
	            	// force historic view with additional info, who and when already completed task
	            	request.setAttribute(TASK_HISTORY_PARAM, true);
	            	Object[] arguments = new Object[2];
	            	arguments[0] = historyTask.getAssignee();
	            	arguments[1] = new SimpleDateFormat(InfrastructureConstants.ISO_DATE_TIME_FORMAT)
            			.format(historyTask.getEndTime());
	            	model.addAttribute(MODEL_ATTR_TASK_COMPLETED, 
            			messageSource.getMessage(InfrastructureConstants.TASK_ALREADY_COMPLETED,
	    				arguments, InfrastructureConstants.UNDEFINED_MESSAGE, request.getLocale()));
            	} else {
            		// there is not living, nor historic instance, task is non existing / was deleted
            		request.getSession()
            			.setAttribute(InfrastructureConstants.SESSION_ATTR_FROM_NON_EXISTING_TASK, true);
            		return "redirect:/" + getLinkedObjectViewPath() + 
            				WebUtil.encodeUrlPathSegment(linkedObject.getId().toString(), 
        						request);
            	}
        	}
        } else {
        	request.setAttribute(TASK_HISTORY_PARAM, true);
        	historyTask = getHistoryTask(id);
        } 
		String taskName = currentTask != null ? currentTask.getName() : historyTask.getName();
		// taskId is the same for active or history process task
		String taskId = currentTask != null ? currentTask.getId() : historyTask.getId();
		if (Boolean.FALSE.equals(historyParam)) {
			request.getSession().setAttribute(SESSION_ATTR_TASK_ID, taskId);
			request.getSession().setAttribute(SESSION_ATTR_PROC_ID, procId);
		}
		String path = UNDEFINED_TASK; // unknown task type
		if (taskHandlerMapping.containsKey(taskName)) {
			path = taskName;
			request.setAttribute(TASK_NAME_PARAM, taskName);
			ITaskDataHandler<ITaskDataEntity, IProcessEntity> handler = taskHandlerMapping.get(path);
			Map<String,?> data = handler.loadTaskData(taskId, linkedObject, historyParam);
			model.addAllAttributes(data);
        }
		model.addAttribute(getLinkedObjectAttrName(), linkedObject);	
        return getTaskViewPath() + path;
	}

	@Override
	@RequestMapping(params = TASK_NAME_PARAM, method = {RequestMethod.POST, RequestMethod.PUT})
	public String saveTask(Model model, HttpServletRequest request) throws TaskHandlerException {
    	String path = request.getParameter(TASK_NAME_PARAM);
    	String taskId = (String) request.getSession().getAttribute(SESSION_ATTR_TASK_ID);
    	String procId = (String) request.getSession().getAttribute(SESSION_ATTR_PROC_ID);
    	IProcessEntity linkedObject = getLinkedObject(procId);
    	if (taskHandlerMapping.containsKey(path)) {
			ITaskDataHandler<ITaskDataEntity, IProcessEntity> handler = taskHandlerMapping.get(path);
	    	ITaskDataEntity taskData = handler.bindTaskData(request, linkedObject);
			TaskDataUtil.setLastUpdate(taskData, ContextUtil.getLoggedUsername());
    		if (RequestMethod.PUT.name().equals(request.getMethod())) {
    			handler.saveTaskData(taskData);
    		} else if (RequestMethod.POST.name().equals(request.getMethod())) {
    			handler.saveTaskDataAndPushProcess(taskData, linkedObject, taskId);
    		}
    	}
    	return "redirect:/" + getLinkedObjectViewPath() + 
    		WebUtil.encodeUrlPathSegment(linkedObject.getId().toString(), request);
	}
	
	@Override
	@RequestMapping(value = "/lock", params = TASK_NAME_PARAM, 
		method = RequestMethod.POST, headers = "Accept=application/json")	
	public @ResponseBody TaskAvailabilityResponse lockTask(HttpServletRequest request) {
		String path = request.getParameter(TASK_NAME_PARAM);
		String taskId = (String) request.getSession().getAttribute(SESSION_ATTR_TASK_ID);
		ITaskDataHandler<ITaskDataEntity, IProcessEntity> handler = taskHandlerMapping.get(path);
		return handler.requestTaskAvailabilityAndLockTask(taskId, 
			request.getSession().getId(), request.getLocale());		
	}
	
	@Override
	@RequestMapping(value = "/requestAvailability", params = TASK_NAME_PARAM, 
		method = RequestMethod.GET, headers = "Accept=application/json")	
	public @ResponseBody TaskAvailabilityResponse requestTaskAvailability(HttpServletRequest request) {
		String path = request.getParameter(TASK_NAME_PARAM);
		String taskId = (String) request.getSession().getAttribute(SESSION_ATTR_TASK_ID);
		ITaskDataHandler<ITaskDataEntity, IProcessEntity> handler = taskHandlerMapping.get(path);
		return handler.requestTaskAvailability(taskId, request.getLocale());
	}
	
}
